﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.IO;
using System.Xml;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Obsidian.Utilities;

namespace Obsidian.Web
{
    public class _JsLibrary
    {

        private DateTime configModified = DateTime.MinValue;
        private string configPath;
        private string configDir;
        private string libDirPath;

        #region CSS

        private LibIncludeWay _cssIncludeWay = LibIncludeWay.NotSet;
        public LibIncludeWay CssIncludeWay
        {
            get { return this._cssIncludeWay; }
        }

        private LibFile _cssMergedFile;
        public LibFile CssMergedFile
        {
            get { return this._cssMergedFile; }
        }

        private LibFile _cssCompiledFile;
        public LibFile CssCompiledFile
        {
            get { return this._cssCompiledFile; }
        }

        private List<LibFile> _cssFiles;
        public List<LibFile> CssFiles
        {
            get { return this._cssFiles; }
        }

        private void ReadCssSeting(XmlDocument xmlDoc)
        {
            XmlNode nodeCss = xmlDoc.SelectSingleNode("config/css");
            if (nodeCss != null)
            {
                this._cssIncludeWay = this.GetIncludeWay(nodeCss);
                this._cssMergedFile = this.GetMergedFile(nodeCss);
                this._cssCompiledFile = this.GetCompiledFile(nodeCss);
                this._cssFiles = this.GetSourceFiles(nodeCss);
            }
        }

        #endregion

        #region JavaScript

        private LibIncludeWay _jsIncludeWay = LibIncludeWay.NotSet;
        public LibIncludeWay JsIncludeWay
        {
            get { return this._jsIncludeWay; }
        }

        private LibFile _jsMergedFile;
        public LibFile JsMergedFile
        {
            get { return this._jsMergedFile; }
        }

        private LibFile _jsCompiledFile;
        public LibFile JsCompiledFile
        {
            get { return this._jsCompiledFile; }
        }

        private List<LibFile> _jsFiles;
        public List<LibFile> JsFiles
        {
            get { return this._jsFiles; }
        }

        private void ReadJsSeting(XmlDocument xmlDoc)
        {
            XmlNode nodeJs = xmlDoc.SelectSingleNode("config/javascript");
            if (nodeJs != null)
            {
                this._jsIncludeWay = this.GetIncludeWay(nodeJs);
                this._jsMergedFile = this.GetMergedFile(nodeJs);
                this._jsCompiledFile = this.GetCompiledFile(nodeJs);
                this._jsFiles = this.GetSourceFiles(nodeJs);
            }
        }

        #endregion

        public _JsLibrary(string configPath)
        {
            this.configPath = HttpContext.Current.Server.MapPath(configPath);
            int n = configPath.LastIndexOf("/");
            this.libDirPath = configPath.Substring(0, n);
        }

        public DateTime Modified
        {
            get { return this.configModified; }
        }

        private void Prepare()
        {
            this.ReadSettingInfo();
            if (AppConfig.JsLibCheckForChanged)
            {
                if (this.CssIncludeWay == LibIncludeWay.Merge ||
                    this.CssIncludeWay == LibIncludeWay.Compile)
                {
                    this.CheckCssMergeAndCompile();
                }
                if (this.JsIncludeWay == LibIncludeWay.Merge ||
                    this.JsIncludeWay == LibIncludeWay.Compile)
                {
                    this.CheckJsMergeAndCompile();
                }
            }
        }

        public void Include(Page page)
        {
            this.Prepare();
            switch (this.CssIncludeWay)
            {
                case LibIncludeWay.Compile:
                    WebUtility.IncludeCss(page, this.CssCompiledFile.IncludePath);
                    break;

                case LibIncludeWay.Merge:
                    WebUtility.IncludeCss(page, this.CssMergedFile.IncludePath);
                    break;

                case LibIncludeWay.Sources:
                    bool isChange = true;
                    IList<LibFile> fileList = this.GetFileInfo(this._cssFiles, out isChange);
                    int cssCount = fileList.Count;
                    string[] cssFiles = new string[cssCount];
                    for (int i = 0; i < cssCount; i++)
                        cssFiles[i] = fileList[i].IncludePath;
                    WebUtility.IncludeCss(page, cssFiles);
                    break;
            }
            switch (this.JsIncludeWay)
            {
                case LibIncludeWay.Compile:
                    WebUtility.IncludeJs(page, this.JsCompiledFile.IncludePath);
                    break;

                case LibIncludeWay.Merge:
                    WebUtility.IncludeJs(page, this.JsMergedFile.IncludePath);
                    break;

                case LibIncludeWay.Sources:
                    bool isChange = true;
                    IList<LibFile> fileList = this.GetFileInfo(this._jsFiles, out isChange);
                    int jsCount = fileList.Count;
                    string[] jsFiles = new string[jsCount];
                    for (int j = 0; j < jsCount; j++)
                        jsFiles[j] = fileList[j].IncludePath;
                    WebUtility.IncludeJs(page, jsFiles);
                    break;
            }
        }
        
        private const string CSS_INCLUDE_HTML = "<link href=\"{0}\" type=\"text/css\" rel=\"stylesheet\">";
        private const string JS_INCLUDE_HTML = "<script src=\"{0}\" type=\"text/javascript\" langugae=\"javascript\"></script>";
        public string CreateIncludeHtml()
        {
            this.Prepare();
            StringBuilder sbHtml = new StringBuilder();
            switch (this.CssIncludeWay)
            {
                case LibIncludeWay.Compile:
                    sbHtml.Append(String.Format(CSS_INCLUDE_HTML, this.CssCompiledFile.IncludePath));
                    break;
                case LibIncludeWay.Merge:
                    sbHtml.Append(String.Format(CSS_INCLUDE_HTML, this.CssMergedFile.IncludePath));
                    break;
                case LibIncludeWay.Sources:
                    bool isChange = true;
                    IList<LibFile> fileList = this.GetFileInfo(this._cssFiles, out isChange);
                    foreach (LibFile file in fileList)
                        sbHtml.Append(String.Format(CSS_INCLUDE_HTML, file.IncludePath));
                    break;
            }
            if (this.CssIncludeWay != LibIncludeWay.NotSet)
                sbHtml.AppendLine("");

            switch (this.JsIncludeWay)
            {
                case LibIncludeWay.Compile:
                    sbHtml.Append(String.Format(JS_INCLUDE_HTML, this.JsCompiledFile.IncludePath));
                    break;
                case LibIncludeWay.Merge:
                    sbHtml.Append(String.Format(JS_INCLUDE_HTML, this.JsMergedFile.IncludePath));
                    break;
                case LibIncludeWay.Sources:
                    bool isChange = true;
                    IList<LibFile> fileList = this.GetFileInfo(this._jsFiles, out isChange);
                    foreach (LibFile file in fileList)
                        sbHtml.Append(String.Format(JS_INCLUDE_HTML, file.IncludePath));
                    break;
            }
            if (this.JsIncludeWay != LibIncludeWay.NotSet)
                sbHtml.AppendLine("");

            return sbHtml.ToString();
        }

        /// <summary>
        ///  读取配置信息
        /// </summary>
        private void ReadSettingInfo()
        {
            FileInfo file = new FileInfo(this.configPath);
            if (file.LastWriteTime != this.Modified)
            {
                this.configModified = file.LastWriteTime;
                this.configDir = file.Directory.FullName;
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(this.configPath);
                this.ReadCssSeting(xmlDoc);
                this.ReadJsSeting(xmlDoc);
                this.configModified = file.LastWriteTime;
            }
        }

        private void CheckCssMergeAndCompile()
        {
            if (this._cssFiles != null)
            {
                bool isChange = true;
                this.GetFileInfo(this._cssFiles, out isChange);

                if (!isChange)
                {
                    if (!File.Exists(this.CssMergedFile.FullName) ||
                        !File.Exists(this.CssCompiledFile.FullName))
                    {
                        isChange = true;
                    }
                    else
                    {
                        FileInfo mergeFile = new FileInfo(this.CssMergedFile.FullName);
                        FileInfo compFile = new FileInfo(this.CssCompiledFile.FullName);
                        if (mergeFile.Length == 0 || compFile.Length == 0)
                            isChange = true;
                    }
                }

                if (isChange)
                {
                    this.MergeFiles(this.CssMergedFile, this.CssFiles);
                    JarInfo jar = AppConfig.GetYuiCompressorInfo();
                    string cmdText = jar.Cmd
                        .Replace("%merged_file%", this.CssMergedFile.FullName)
                        .Replace("%output_file%", this.CssCompiledFile.FullName);
                    DosUtility.Execute(cmdText);
                }
            }
        }
        private void CheckJsMergeAndCompile()
        {
            if (this._jsFiles != null)
            {
                bool isChange = true;
                this.GetFileInfo(this._jsFiles, out isChange);

                if (!isChange)
                {
                    if (!File.Exists(this.JsMergedFile.FullName) ||
                        !File.Exists(this.JsCompiledFile.FullName))
                    {
                        isChange = true;
                    }
                    else
                    {
                        FileInfo mergeFile = new FileInfo(this.JsMergedFile.FullName);
                        FileInfo compFile = new FileInfo(this.JsCompiledFile.FullName);
                        if (mergeFile.Length == 0 || compFile.Length == 0)
                            isChange = true;
                    }
                }

                if (isChange)
                {
                    this.MergeFiles(this.JsMergedFile, this.JsFiles);
                    JarInfo jar = AppConfig.GetClosureCompilerInfo();
                    string cmdText = jar.Cmd
                        .Replace("%merged_file%", this.JsMergedFile.FullName)
                        .Replace("%output_file%", this.JsCompiledFile.FullName);
                    DosUtility.Execute(cmdText);
                }
            }
        }


        #region 读取配置信息

        private LibIncludeWay GetIncludeWay(XmlNode node)
        {
            XmlAttribute attr = node.Attributes["includeWay"];
            if (attr == null)
                return LibIncludeWay.NotSet;
            string strIncWay = attr.Value;
            switch(strIncWay)
            {
                case "sources":
                    return LibIncludeWay.Sources;
                case "merge":
                    return LibIncludeWay.Merge;
                case "compile":
                    return LibIncludeWay.Compile;
                default:
                    throw new Exception("includeWay值必须是sources、merge或compile其中一项");
            }
        }


        private LibFile GetMergedFile(XmlNode node)
        {
            XmlNode nodeMergeFile = node.SelectSingleNode("mergedFile");
            if (nodeMergeFile == null)
                return null;
            LibFile file = new LibFile();
            file.Name = Convert.ToString(nodeMergeFile.InnerText);
            file.FullName = this.ServerMapPath(file.Name);
            if (File.Exists(file.FullName))
                file.IncludePath = this.GetIncludePath(file.FullName);
            return file;
        }

        private LibFile GetCompiledFile(XmlNode node)
        {
            XmlNode nodeCompiledFile = node.SelectSingleNode("compiledFile");
            if (nodeCompiledFile == null)
                return null;
            LibFile file = new LibFile();
            file.Name = Convert.ToString(nodeCompiledFile.InnerText);
            file.FullName = this.ServerMapPath(file.Name);
            if (File.Exists(file.FullName))
                file.IncludePath = this.GetIncludePath(file.FullName);
            return file;
        }

        private List<LibFile> GetSourceFiles(XmlNode node)
        {
            XmlNodeList nodeSourceFiles = node.SelectNodes("sourceFiles/file");
            if (nodeSourceFiles == null)
                return null;
            List<LibFile> fileList = new List<LibFile>();
            foreach (XmlNode nodeSourceFile in nodeSourceFiles)
            {
                LibFile f = new LibFile();
                f.Name = Convert.ToString(nodeSourceFile.InnerText);
                fileList.Add(f);
            }
            return fileList;
        }

        #endregion

        private void MergeFiles(LibFile mergedFile, List<LibFile> fileList)
        {
            StringBuilder sb = new StringBuilder();
            foreach (LibFile f in fileList)
                sb.AppendLine(IOUtility.ReadTextFromFile(f.FullName, "utf-8"));
            string path = mergedFile.FullName;
            IOUtility.WriterTextToFile(path, false, sb.ToString());
            mergedFile.IncludePath = this.GetIncludePath(mergedFile.FullName);
        }

        private string ServerMapPath(string file)
        {
            int n = file.IndexOf('/');
            if (n != 0)
                file = this.libDirPath + "/" + file;
            return HttpContext.Current.Server.MapPath(file);
        }

        private string GetIncludePath(string fullName)
        {
            FileInfo f = new FileInfo(fullName);
            return this.libDirPath + "/" + f.Name + "?t=" + f.LastWriteTime.ToString("mmss");
        }

        private List<LibFile> GetFileInfo(List<LibFile> fileList, out bool isChanged)
        {
            isChanged = false;
            HttpServerUtility serveUtil = HttpContext.Current.Server;
            foreach (LibFile f in fileList)
            {
                f.FullName = serveUtil.MapPath(this.libDirPath + "/" + f.Name);
                FileInfo file = new FileInfo(f.FullName);
                string modified = file.LastWriteTime.ToString("yyyy-MM-dd hh:mm:ss");
                if (f.IncludePath == null || !modified.Equals(f.Modified))
                {
                    f.Modified = modified;
                    f.IncludePath = this.libDirPath + "/" + f.Name + "?t=" + file.LastWriteTime.ToString("mmss");
                    if (isChanged == false)
                        isChanged = true;
                }
            }
            return fileList;
        }


        public static void Include(Page page, params string[] libNames)
        {
            foreach (string name in libNames)
            {
                _JsLibrary lib = AppConfig.GetJsLibConfig(name);
                if (lib == null)
                    throw new Exception("JS库" + name + "配置不存在");
                lib.Include(page);
            }
        }

        public static string IncludeHtml(string libs)
        {
            StringBuilder sb = new StringBuilder();
            string[] arrLibs = libs.Split(',');
            foreach (string name in arrLibs)
            {
                _JsLibrary lib = AppConfig.GetJsLibConfig(name);
                if (lib == null)
                    throw new Exception("JS库" + name + "配置不存在");
                sb.AppendLine(lib.CreateIncludeHtml());
            }
            return sb.ToString();
        }
        

    }

    public class LibFile
    {
        public string Name { get; set; }
        public string FullName { get; set; }
        public string Modified { get; set; }
        public string IncludePath { get; set; }
    }

    public enum LibIncludeWay
    {
        /// <summary>
        /// 未设置
        /// </summary>
        NotSet,
        /// <summary>
        /// 引用原文件
        /// </summary>
        Sources,
        /// <summary>
        /// 引用合并文件
        /// </summary>
        Merge,
        /// <summary>
        /// 引用压缩文件
        /// </summary>
        Compile
    }

}
